﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using EnvDTE;

using EnvDTE80;

namespace eVision.SharpSort
{
    public class CodeFormatter
    {
		#region Classes (1)
		public class GroupSettings
        {
			#region Properties (5)
			public int PaddingAfter { get; set; }
			public int PaddingBefore { get; set; }
			public int PaddingBetween { get; set; }
			public String RegionName { get; set; }
			public bool ShowRegion { get; set; }
			#endregion

			#region Constructors (1)
			public GroupSettings()
            {
                PaddingBefore = 0;
                PaddingAfter = 0;
                PaddingBetween = 1;
                ShowRegion = true;
            }
			#endregion
        }
		#endregion

		#region Enums (1)
		public enum MemberType { Import, Namespace, Class, Struct, Interface, Enum, Field, Delegate, Event, Constructor, Property, Method, Other };
		#endregion

		#region Static fields (6)
		private static ICollection<MemberType> dontSortMembers = new HashSet<MemberType>()
        {
            MemberType.Namespace,
        };
		private static ICollection<MemberType> sortInsideMembersOfType = new HashSet<MemberType>()
        {
            MemberType.Namespace,
            MemberType.Class,
            MemberType.Struct,
            MemberType.Interface
        };
		private static ICollection<MemberType> sortableMembers = new HashSet<MemberType>(){
                MemberType.Class,
                MemberType.Struct,
                MemberType.Interface,
                MemberType.Enum,
                MemberType.Field, 
                MemberType.Delegate, 
                MemberType.Event, 
                MemberType.Property,
                MemberType.Constructor, 
                MemberType.Method
        };

		private static IDictionary<MemberType, GroupSettings> settingByGroup = new Dictionary<MemberType, GroupSettings>()
        {
            { MemberType.Import, new GroupSettings() { RegionName = "Imports" , PaddingBetween = 1, PaddingAfter = 0, ShowRegion = false}},
            { MemberType.Class, new GroupSettings() { RegionName = "Classes" , PaddingBetween = 1, ShowRegion = false}},
            { MemberType.Struct, new GroupSettings() { RegionName = "Structs" , PaddingBetween = 1, ShowRegion = false}},
            { MemberType.Interface, new GroupSettings() { RegionName = "Interfaces" , PaddingBetween = 1, ShowRegion = false}},
            { MemberType.Enum, new GroupSettings() { RegionName = "Enums" , PaddingBetween = 1}},
            { MemberType.Field, new GroupSettings() { RegionName = "Fields", PaddingBetween = 0 }},
            { MemberType.Delegate, new GroupSettings() { RegionName = "Delegates", PaddingBetween = 0}},
            { MemberType.Event, new GroupSettings() { RegionName = "Events" , PaddingBetween = 0}},
            { MemberType.Constructor, new GroupSettings() { RegionName = "Constructors" , PaddingBetween = 1}},
            { MemberType.Property, new GroupSettings() { RegionName = "Properties" , PaddingBetween = 1}},
            { MemberType.Method, new GroupSettings() { RegionName = "Methods" , PaddingBetween = 1}},
            { MemberType.Other, new GroupSettings() { RegionName = null , PaddingBetween = 1}},
                };

		public static String nl = "\r\n";

		#endregion

		#region Static methods (13)
		private static void AddEmptyLines(StringBuilder sb, int number)
        {
            AddSequenceCharacters(sb, number, nl);
        }

		private static void AddSequenceCharacters(StringBuilder sb, int number, String str)
        {
            for (int i = 0; i < number; ++i)
                sb.Append(str);
        }

		private static bool DontSortThisMember(MemberType memberType)
        {
            return dontSortMembers.Contains(memberType);
        }

		private static void FindSortableMembers(CodeElements elements, RegionDefinition regionDefinition, FileCodeModel model, bool deleteRest)
        {
            foreach (CodeElement element in elements)
            {
                switch (element.Kind)
                {
                    case vsCMElement.vsCMElementNamespace:
                        SortMembers(element, model, regionDefinition, 1, deleteRest);
                        break;
                    case vsCMElement.vsCMElementClass:
                        SortMembers(element, model, regionDefinition, 1, deleteRest);
                        break;
                    case vsCMElement.vsCMElementStruct:
                        SortMembers(element, model, regionDefinition, 1, deleteRest);
                        break;
                    case vsCMElement.vsCMElementInterface:
                        SortMembers(element, model, regionDefinition, 1, deleteRest);
                        break;
                    case vsCMElement.vsCMElementImportStmt:
                        //SortMembers(element, element.Children, model);
                        break;
                    default:
                        throw new ArgumentException("What the fuck are u trying to sort man? " + element.Kind.ToString());
                }
            }
        }


        private static bool HasCompilerErrors(DTE2 ide, ProjectItem item)
        {       
            ErrorList errors = ide.ToolWindows.ErrorList;
        
            short numberOfFiles = item.FileCount;
            ErrorItems errorItems = errors.ErrorItems;
            int errorCount = errorItems.Count;

            for (int i = 1; i <= errorCount; ++i)
            {
                ErrorItem error = errorItems.Item(i);
                if (error.ErrorLevel == vsBuildErrorLevel.vsBuildErrorLevelHigh)
                {
                    for (short j = 0; j < numberOfFiles; ++j)
                    {
                        if (string.Compare(error.FileName, item.get_FileNames(j), true) == 0)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

		public static void FormatCode(Document document, String layoutPath, bool deleteRest)
        {
            try
            {
                DTE2 dte = (DTE2)document.DTE;
                ProjectItem projectItem = document.ProjectItem;
                FileCodeModel fcm = projectItem.FileCodeModel;
                RegionDefinition regionDefinition = null;

                if (HasCompilerErrors(dte, projectItem))
                {
                    M("Current item has compiler errors, correct them and try again.");
                    return;
                }

                try
                {
                    regionDefinition = Serializer.XmlDeserialize<RegionDefinition>(File.ReadAllText(layoutPath));
                }
                catch (Exception ex)
                {
                    if (ex.InnerException != null)
                        throw ex.InnerException;
                    else
                        throw;
                }

                dte.SuppressUI = true;

                try
                {
                    if (document == null)
                        throw new ArgumentException("Active document doesn't exist.");

                    SortUsings(fcm);
                    FindSortableMembers(fcm.CodeElements, regionDefinition, fcm, deleteRest);
                }
                finally
                {
                    dte.SuppressUI = false;
                }
            }
            catch (Exception ex)
            {
                M(ex.Message + "\n" + ex.StackTrace);
            }
        }

		private static String GetFirstElementOfNamespace(String namespaceName)
        {
            if (namespaceName.Contains("."))
                return namespaceName.Substring(0, namespaceName.IndexOf("."));
            else
                return namespaceName;
        }

		public static MemberType GetMemberFromCodeElementType(CodeElement element)
        {
            switch (element.Kind)
            {
                case vsCMElement.vsCMElementNamespace:
                    return MemberType.Namespace;
                case vsCMElement.vsCMElementClass:
                    return MemberType.Class;
                case vsCMElement.vsCMElementStruct:
                    return MemberType.Struct;
                case vsCMElement.vsCMElementInterface:
                    return MemberType.Interface;
                case vsCMElement.vsCMElementEnum:
                    return MemberType.Enum;
                case vsCMElement.vsCMElementVariable:
                    return MemberType.Field;
                case vsCMElement.vsCMElementDelegate:
                    return MemberType.Delegate;
                case vsCMElement.vsCMElementEvent:
                    return MemberType.Event;
                case vsCMElement.vsCMElementFunction:
                    CodeFunction function = element as CodeFunction;
                    if (function.FunctionKind == vsCMFunction.vsCMFunctionConstructor || function.FunctionKind == vsCMFunction.vsCMFunctionDestructor)
                        return MemberType.Constructor;
                    else
                        return MemberType.Method;
                case vsCMElement.vsCMElementProperty:
                    return MemberType.Property;
                default:
                    return MemberType.Other;
            }
        }

		public static void M(String message)
        {
            MessageBox.Show(message);
        }

		private static bool RecursiveSortInsideMembersOfType(MemberType memberType)
        {
            return sortInsideMembersOfType.Contains(memberType);
        }

		private static void RemoveOldElements(IEnumerable<CodeMemberInfo> elemenentsToRemove, CodeElement containerElement, FileCodeModel model)
        {
            MemberType memberType;
            MemberType thisMemberType = GetMemberFromCodeElementType(containerElement);
            CodeClass typedContainerClassElement = containerElement as CodeClass;
            CodeStruct typedContainerStructElement = containerElement as CodeStruct;
            CodeInterface typedContainerInterfaceElement = containerElement as CodeInterface;

            foreach (CodeMemberInfo cmi in elemenentsToRemove)
            {
                memberType = GetMemberFromCodeElementType(cmi.CodeElement);
                CodeElement elementToRemove = cmi.CodeElement;

                cmi.RemoveElementFromLayout();
            }
        }

		private static void SortMembers(CodeElement containerElement, FileCodeModel model, RegionDefinition regionDefinition, int depth, bool deleteRest)
        {
            MemberType memberType;
            memberType = GetMemberFromCodeElementType(containerElement);

            if (!RecursiveSortInsideMembersOfType(memberType)) return;

            ICollection<CodeMemberInfo> codeMembers = new List<CodeMemberInfo>();

            // prvo rekurzivno sortiramo
            foreach (CodeElement childElement in containerElement.Children)
            {
                if (RecursiveSortInsideMembersOfType(GetMemberFromCodeElementType(childElement)))
                {
                    SortMembers(childElement, model, regionDefinition, depth + 1, deleteRest);
                }
            }

            if (DontSortThisMember(GetMemberFromCodeElementType(containerElement)))
                return;

            ICollection<TextRange> textRanges = new HashSet<TextRange>();
            foreach (CodeElement childElement in containerElement.Children)
            {
                if (SortableMembers(GetMemberFromCodeElementType(childElement)))
                    codeMembers.Add(new CodeMemberInfo(childElement, textRanges));
            }

            //gradenje koda
            ICollection<CodeMemberInfo> doneElements = new List<CodeMemberInfo>();
            bool isEmpty = false;

            String sourceCode = regionDefinition.GenerateCode(depth, codeMembers, doneElements, out isEmpty) + nl;

            if (deleteRest)
            {
                EditPoint startPoint = containerElement.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint();
                EditPoint endPoint = containerElement.GetEndPoint(vsCMPart.vsCMPartBody).CreateEditPoint();

                startPoint.Delete(endPoint);

                startPoint.Insert(sourceCode);
            }
            else
            {
                sourceCode += nl + "// OTHER ELEMENTS " + nl;

                RemoveOldElements(doneElements, containerElement, model);

                EditPoint insertPoint = containerElement.GetStartPoint(vsCMPart.vsCMPartBody).CreateEditPoint();
                insertPoint.Insert(sourceCode);
            }
        }

		private static void SortUsings(FileCodeModel codeModel)
        {
            codeModel.DTE.ExecuteCommand("Edit.RemoveAndSort", "");

            String lastNamespaceName = null;
            int lastLineNumber = 0;
            foreach (CodeElement codeElement in codeModel.CodeElements)
            {
                if (codeElement.Kind == vsCMElement.vsCMElementImportStmt)
                {
                    CodeImport importCode = (CodeImport)codeElement;
                    int lineNumber = importCode.EndPoint.Line;
                    String currentNamespaceStartName = GetFirstElementOfNamespace(importCode.Namespace);

                    if (lastNamespaceName != null)
                    {
                        if (lastNamespaceName != currentNamespaceStartName && importCode.StartPoint.Line == lastLineNumber + 1)
                        {
                            EditPoint startOfUsing = importCode.StartPoint.CreateEditPoint();
                            startOfUsing.Insert(nl);
                            lineNumber++;
                        }
                    }

                    lastNamespaceName = currentNamespaceStartName;
                    lastLineNumber = lineNumber;

                    //M(currentNamespaceStartName);
                    //M(lastLineNumber.ToString());
                }
            }
        }
        
		private static bool SortableMembers(MemberType memberType)
        {
            return sortableMembers.Contains(memberType);
        }
		#endregion
    }
}
